#include "transplot.h"

#include <iostream>
#include <QSize>
#include <QRect>

#include "TransRainbow.h"
#include "TransPlotHelper.h"

TransPlot::TransPlot(const QwtText& title, QWidget * parent)
    : QwtPlot(title, parent)
    , map_series()
    , jcbmatch()
    , heigh_series_next(HEIGH_SERIES)
    , width_max(200)
    , color_id(0)
    , trans_lock(false)
    , isDefaultRectUpdated(false)
{
    // Background grid
    grid = new QwtPlotGrid;
    grid->setMajorPen( Qt::gray, 0, Qt::DotLine );
    grid->attach( this );

    // Initial Axis scale
    setAxisScale(QwtPlot::yLeft, 0, 10);
    setAxisScale(QwtPlot::xBottom,0,width_max);
    setAxisAutoScale(QwtPlot::xBottom, false);

    enableAxis(QwtPlot::yLeft, false);
    // Set up zoomer
    zoomer = new Zoomer( canvas() );
    zoomer->setPlot(this);
}

TransPlot::~TransPlot() {
    delete zoomer;
    delete grid;
    type_series::const_iterator i = map_series.constBegin();
    while (i != map_series.constEnd()) {
        delete ( (TransSeries*)i.value() );
        i++;
    }
}

/**
 * @brief TransPlot::addTransaction
 * @param transaction
 *
 * Add given transaction to this plot.
 * If transplot cannot find a transaction series with the same transaction name.
 * It will create a new one.
 */
void TransPlot::addTransaction(const Transaction& transaction) {
    QString Name = transaction.getName();

    QMap<QString, TransSeries*>::iterator result = map_series.find(Name);

    if (result != map_series.end()) {
        // Find matched series.
        TransSeries* series = result.value();
        series->addTransaction(transaction);

//        std::cout << __FILE__ << ", add transaction" << std::endl;
    }
    else {
        // Cannot find this series, create a new series.
        TransSeries* series = new TransSeries(Name);
        series->addTransaction(transaction);
        series->setHeigh(0);
        map_series.insert(Name, series);

        // Set Height
        heigh_series_next += HEIGH_SERIES;
        series->setHeigh(heigh_series_next);

        // Pickup a color
        series->setColor( TransRainbow::getRainbow(color_id) );
        color_id += 1; if (color_id > 6) color_id = 0;

        std::cout << "Create series: " << Name.toStdString() << ", color = " << color_id << std::endl;
    }
    int width_margin = 100;
    if (width_max < transaction.getTime()) width_max = transaction.getTime() + width_margin;

    // Adjust axis scale.
    setAxisScale(QwtPlot::yLeft, 0, heigh_series_next + 2);
    setAxisScale(QwtPlot::xBottom, 0, width_max + 10);
    updateAxes();

}

void TransPlot::drawCanvas(QPainter *painter) {

    QwtPlot::drawCanvas(painter);
    if ( !getLock() ) return;
    Lock();

    // First calculate the lastest width and Height

    if (defaultRectOfTransPlot.width() < width_max || defaultRectOfTransPlot.height() < heigh_series_next) {
        defaultRectOfTransPlot = QRectF(0, 0, width_max, heigh_series_next + 2);
        zoomer->setZoomBase(defaultRectOfTransPlot);
    }

    if (!isDefaultRectUpdated) updateDefaultRectFrame(painter);


    type_series::ConstIterator const_i = map_series.constBegin();

    while (const_i != map_series.constEnd()) {

        const TransSeries* series = const_i.value();
        int height = series->getHeigh();
        // Skip the series out of DrawRect
        if (height < drawRectOfTransPlot.y() || height > (drawRectOfTransPlot.height() + drawRectOfTransPlot.y()) ) {
            const_i++;
            continue;
        }
        drawTransSeries(painter, series);

        const_i++;
    }

//    anaylsisConnectionAndDraw(painter, "JCBSLOT0", "JCBRESPSLOT0", &jcbmatch);
    unLock();
}

void TransPlot::drawTransSeries(QPainter *painter, const TransSeries* series) const
{
    QVector<Transaction>::ConstIterator const_trans = series->getSeries().constBegin();
    int begin = TransPlotHelper::getSeriesPosByTime(*series, drawRectOfTransPlot.x());
    const_trans += begin;

    while (const_trans != series->getSeries().constEnd()) {
        bool skipContent = false;
        const Transaction& transaction_draw = *const_trans;
        drawTransaction(painter, transaction_draw, series->getHeigh(), series->getColor(), skipContent);

        int x = QwtPlot::transform(QwtPlot::xBottom, transaction_draw.getTime());

        // Try to skip some trans;
        int skip_index = 1;
        while (skip_index < 1000) {
            QVector<Transaction>::ConstIterator const_trans_next = const_trans + skip_index;
            if (const_trans_next == series->getSeries().constEnd()) {
                break;
            }

            const Transaction& next = *const_trans_next;
            int x_next = QwtPlot::transform(QwtPlot::xBottom, next.getTime());
            if ( (x_next - x) < 20 ) {
                // Continue searching
            }
            else{
                break;
            }
            skip_index++;
        }
        const_trans += skip_index;
    }
}

void TransPlot::drawTransaction(QPainter *painter, const Transaction& transaction, int heigh, const QColor& color, const bool skipContent) const
{
    // If the transaction is outside drawRect, just skip
    bool isTransInRect = isTransactionInDrawRect(transaction, heigh);
    if ( !isTransInRect ) return;

    int x = QwtPlot::transform(QwtPlot::xBottom, transaction.getTime());
    int y = QwtPlot::transform(QwtPlot::yLeft, heigh);

    // next transaction and current is too close, don't draw the content
    QPoint lefttop(x,y);

    int trans_heigh = drawTransRect(painter, lefttop, transaction, color, skipContent);

    if (!skipContent) {
        drawColum(painter, transaction, lefttop, trans_heigh);
    }
}

void TransPlot::drawColum(QPainter* painter, const Transaction& transaction, const QPoint& lefttop, int heigh) const
{
    QMap<QString, QString>::ConstIterator i = transaction.getColum().constBegin();
    int x = lefttop.x();
    int y_step = lefttop.y();

    painter->setPen( QPen(QBrush(Qt::black), 1) );
    // Draw Name
    painter->drawText(QPoint(x, y_step), transaction.getName());
    y_step += heigh;
    // Draw Content

    while ( i != transaction.getColum().constEnd() ) {
        QPoint point(x, y_step);
        QString str_colum = i.key() + " : " + i.value();
        painter->drawText(point, str_colum.toLocal8Bit().data());
        y_step += heigh;

        i++;
    }
}

int TransPlot::drawTransRect(QPainter *painter, const QPoint& lefttop, const Transaction &transaction, const QColor& color, const bool skipContent) const
{

    int width = 0;
    int heigh = 0;
    int heigh_text = 0;

    QMap<QString, QString>::ConstIterator i = transaction.getColum().constBegin();
    int columCount = transaction.getColum().size();

    heigh_text = defaultTransPlotRectHeigh;
    heigh = defaultTransPlotRectHeigh * columCount;

    if (skipContent) {
        width = defaultTransPlotRectWidth;
    }
    else {

        QFont plot_font = painter->font();
        QFontMetrics qfm(plot_font);
        while ( i != transaction.getColum().constEnd() ) {
            QString str_colum = i.key() + " : " + i.value();
            QSize size_colum = qfm.size(0, str_colum.toLocal8Bit().data());

            if (width < size_colum.width()) width = size_colum.width();
            i++;
        }
    }
    // Include transaction's name as a colum
    heigh += heigh_text;

    QSize size_colum(width, heigh + 2);
    QRect rect_colum(QPoint(lefttop.x(), lefttop.y()-heigh_text), size_colum);
    painter->setPen( QPen(QBrush(Qt::darkGray), 7) );
    painter->drawRect(rect_colum);

    if (!skipContent)
        painter->fillRect(rect_colum, color);

    return heigh_text;
}

void TransPlot::drawConnectionLine(QPainter *painter, const Transaction* trans1, const int heigh1, const Transaction* trans2, const int heigh2)
{
    unsigned int x1 = QwtPlot::transform(QwtPlot::xBottom, trans1->getTime());
    unsigned int y1 = QwtPlot::transform(QwtPlot::yLeft, heigh1);
    QPoint p1(x1,y1);

    unsigned int x2 = QwtPlot::transform(QwtPlot::xBottom, trans2->getTime());
    unsigned int y2 = QwtPlot::transform(QwtPlot::yLeft, heigh2);
    QPoint p2(x2,y2);

    painter->drawLine(p1, p2);

}

bool TransPlot::isTransactionInDrawRect(const Transaction &trans, unsigned int heigh) const
{
    unsigned int x = trans.getTime();
    unsigned int y = heigh;

    int margin_x = 10;
    int margin_y = 10;

//    std::cout << "CheckTrans, x = " << x
//              << ", y = " << y
//              << ", rx = " << drawRect.x()
//              << ", ry = " << drawRect.y()
//              << ", rw = " << drawRect.width()
//              << ", rh = " << drawRect.height()
//              << std::endl;

    bool x_in = (x > drawRectOfTransPlot.x()-margin_x) && (x < drawRectOfTransPlot.x() + drawRectOfTransPlot.width() + margin_x);
    bool y_in = (y > drawRectOfTransPlot.y()-margin_y) && (y < drawRectOfTransPlot.y() + drawRectOfTransPlot.height() + margin_x);

    return (x_in && y_in);
}

void TransPlot::updateDefaultRectFrame(QPainter* painter)
{
    QString default_colum = "Addr:0x123456780";
    QFont plot_font = painter->font();
    QFontMetrics qfm(plot_font);
    QSize size_colum = qfm.size(0, default_colum.toLocal8Bit().data());
    defaultTransPlotRectWidth = size_colum.width();
    defaultTransPlotRectHeigh = size_colum.height();
    isDefaultRectUpdated = true;
}

const TransSeries *TransPlot::findSeriesByName(const QString &Name, bool& found) const
{
    QMap<QString, TransSeries*>::ConstIterator iter = map_series.find(Name);
    if (iter != map_series.constEnd()) {
        found = true;
        return iter.value();
    }
    else {
        found = false;
        return (TransSeries*)NULL;
    }
}



void TransPlot::anaylsisConnectionAndDraw(QPainter* painter, const QString SeriesMaster, const QString SeriesSlave, TransMatch *match)
{
    const TransSeries* master;
    const TransSeries* slave;
    // Step 1. Find the two series
    bool found1 = false;
    bool found2 = false;
    master = findSeriesByName(SeriesMaster, found1);
    slave = findSeriesByName(SeriesSlave, found2);
    if ( ! (found1 && found2) ) return;

    int heigh1 = master->getHeigh();
    int heigh2 = slave->getHeigh();

    TransMatch::TransMatchVector connections;
    match->getConnections(*master, *slave, connections);

    TransMatch::TransMatchVector::ConstIterator iter = connections.constBegin();
    while (iter != connections.constEnd()) {
        const Transaction* trans1 = iter->first;
        const Transaction* trans2 = iter->second;
        drawConnectionLine(painter, trans1, heigh1, trans2, heigh2);

        iter++;
    }

}
